﻿using JavaVirtualMachine.ConstantPoolItems;
using System.Runtime.InteropServices;
using JavaVirtualMachine.Attributes;
using System;

namespace JavaVirtualMachine
{
    public enum MethodInfoFlag
    {
        Public = 0x0001,        //Declared public; may be accessed from outside its package.
        Private = 0x0002,       //Declared private; accessible only within the defining class and other classes belonging to the same nest(§5.4.4).
        Protected = 0x0004,     //Declared protected; may be accessed within subclasses.
        Static = 0x0008,        //Declared static.
        Final = 0x0010,         //Declared final; must not be overridden(§5.4.5).
        Synchronized = 0x0020,  //Declared synchronized; invocation is wrapped by a monitor use.
        Bridge = 0x0040,        //A bridge method, generated by the compiler.
        VarArgs = 0x0080,       //Declared with variable number of arguments.
        Native = 0x0100,        //Declared native; implemented in a language other than the Java programming language.
        Abstract = 0x0400,      //Declared abstract; no implementation is provided.
        Strict = 0x0800,        //Declared strictfp; floating-point mode is FP-strict.
        Synthetic = 0x1000,     //Declared synthetic; not present in the source code.
    }

    public class MethodInfo
    {
        public ushort AccessFlags { get; }
        public ushort NameIndex { get; }
        public ushort DescriptorIndex { get; }
        public ushort AttributesCount { get; }
        public AttributeInfo[] Attributes;
        public ushort MaxStack;
        public ushort MaxLocals;
        public CodeAttribute CodeAttribute { get; }
        public ExceptionsAttribute ExceptionsAttribute { get; }

        public bool Deprecated = false;
        public byte[] RawAnnotations;
        public byte[] RawParameterAnnotations;

        public string Name;
        public string Descriptor;
        public string RetuenType;
        public ClassFile ClassFile;

        public MethodInfo()
        {

        }
        public MethodInfo(ref ReadOnlySpan<byte> data, ClassFile classFile)
        {
            AccessFlags = data.ReadTwo();

            NameIndex = data.ReadTwo();
            Name = ((CUtf8Info)classFile.Constants[NameIndex]).String;

            DescriptorIndex = data.ReadTwo();
            Descriptor = ((CUtf8Info)classFile.Constants[DescriptorIndex]).String;
            RetuenType = Descriptor.Split(')')[1];
            AttributesCount = data.ReadTwo();
            Attributes = new AttributeInfo[AttributesCount];
            for (int i = 0; i < AttributesCount; i++)
            {
                ushort nameIndexNonSwapped = MemoryMarshal.Cast<byte, ushort>(data)[0];
                ushort nameIndex = nameIndexNonSwapped.SwapEndian();
                string name = ((CUtf8Info)classFile.Constants[nameIndex]).String;
                switch (name)
                {
                    case "Code":
                        CodeAttribute code = new CodeAttribute(ref data, classFile.Constants);
                        Attributes[i] = code;
                        MaxStack = code.MaxStack;
                        MaxLocals = code.MaxLocals;
                        CodeAttribute = code;
                        break;
                    case "Exceptions":
                        ExceptionsAttribute exceptionsAttribute = new ExceptionsAttribute(ref data, classFile.Constants);
                        Attributes[i] = exceptionsAttribute;
                        ExceptionsAttribute = exceptionsAttribute;
                        break;
                    case "Deprecated":
                        Attributes[i] = new DeprecatedAttribute(ref data, classFile.Constants);
                        Deprecated = true;
                        break;
                    case "RuntimeVisibleAnnotations":
                        Attributes[i] = new RuntimeVisibleAnnotationsAttribute(ref data, classFile.Constants);
                        break;
                    case "Synthetic":
                        Attributes[i] = new SyntheticAttribute(ref data, classFile.Constants);
                        break;
                    case "Signature":
                        Attributes[i] = new SignatureAttribute(ref data, classFile.Constants);
                        break;
                    case "RuntimeInvisibleAnnotations":
                    case "RuntimeVisibleParameterAnnotations":
                    case "RuntimeInvisibleParameterAnnotations":
                    case "AnnotationDefault":
                        throw new NotImplementedException(classFile.Name +" "+ name + " MethodInfo Error");
                    default:
                        Attributes[i] = new AttributeInfo(ref data, classFile.Constants);
                        break;

                }
            }

            ClassFile = classFile;

            if (HasFlag(MethodInfoFlag.Native))
            {
                MaxStack = 1;
                MaxLocals = (ushort)(this.NumOfArgs() + (HasFlag(MethodInfoFlag.Static) ? 0 : 1));
            }
        }
        public bool HasFlag(MethodInfoFlag flag)
        {
            return (AccessFlags & (int)flag) != 0;
        }
        public bool IsSignaturePolymorphic()
        {
            return ClassFile.Name == "java/lang/invoke/MethodHandle" &&
                    Descriptor == "([Ljava/lang/Object;)Ljava/lang/Object;" &&
                    HasFlag(MethodInfoFlag.VarArgs) &&
                    HasFlag(MethodInfoFlag.Native);
        }
    }
}
